import cv2
import logging
import os
import subprocess
import tempfile
import shutil
from tqdm import tqdm
import numpy as np

# 设置日志
logging.basicConfig(
    level=logging.INFO,
    format='[video_editor] %(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler('video_editor.log', mode='w', encoding='utf-8')
    ]
)

def robust_convert_video(input_path, output_path=None):
    """
    使用兼容性更好的FFmpeg参数转换视频
    """
    if output_path is None:
        temp_dir = tempfile.gettempdir()
        base_name = os.path.splitext(os.path.basename(input_path))[0]
        output_path = os.path.join(temp_dir, f"{base_name}_fixed.mp4")

    logging.warning(f"开始修复转换: {input_path} -> {output_path}")

    convert_cmd = [
        'ffmpeg',
        '-v', 'error',
        '-i', input_path,
        '-max_muxing_queue_size', '1024',
        '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '23',
        '-c:a', 'aac', '-b:a', '128k',
        '-movflags', '+faststart',
        '-y', output_path
    ]

    try:
        result = subprocess.run(convert_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if b'Error' in result.stderr or b'error' in result.stderr:
            logging.error(f"视频转换可能失败: {result.stderr.decode('utf-8', errors='ignore')[:500]}")
            return None
        logging.info(f"视频修复转换成功: {output_path}")
        return output_path
    except subprocess.CalledProcessError as e:
        logging.warning(f"标准转换失败, 尝试简单转换: {input_path}")

        simple_cmd = [
            'ffmpeg',
            '-v', 'error',
            '-i', input_path,
            '-c:v', 'copy',
            '-c:a', 'copy',
            '-y', output_path
        ]

        try:
            subprocess.run(simple_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            logging.info(f"简单转换成功: {output_path}")
            return output_path
        except subprocess.CalledProcessError as se:
            logging.error(f"视频转换失败: {se.stderr.decode('utf-8', errors='ignore')[:500]}")
            return None
    except Exception as e:
        logging.error(f"转换过程中发生未知错误: {str(e)}")
        return None

def get_video_info(video_path):
    """
    获取视频基本信息
    """
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        raise ValueError(f"无法打开视频文件: {video_path}")

    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    duration = cap.get(cv2.CAP_PROP_FRAME_COUNT) / fps

    cap.release()
    return {
        'fps': fps,
        'width': width,
        'height': height,
        'duration': duration
    }

def safe_load_clip(video_path):
    """安全加载视频信息"""
    try:
        info = get_video_info(video_path)
        logging.info(f"视频直接加载成功: {video_path}")
        return info, None
    except Exception as e:
        logging.warning(f"视频加载失败 ({video_path}), 尝试修复: {str(e)}")

    # 尝试修复转换
    fixed_path = robust_convert_video(video_path)
    if not fixed_path:
        try:
            logging.warning(f"尝试直接加载而不验证: {video_path}")
            info = get_video_info(video_path)
            return info, None
        except:
            raise ValueError(f"无法加载或修复视频: {video_path}")

    try:
        info = get_video_info(fixed_path)
        return info, fixed_path
    except Exception as e2:
        logging.error(f"修复后视频仍无法加载: {fixed_path}, 错误: {str(e2)}")
        raise ValueError(f"修复后视频仍无法加载: {video_path}")

def resize_and_center_frame(frame, target_width, target_height):
    """
    调整帧大小并居中放置
    """
    h, w = frame.shape[:2]

    # 计算缩放比例，保持宽高比
    scale_w = target_width / w
    scale_h = target_height / h
    scale = min(scale_w, scale_h)

    # 缩放帧
    new_w = int(w * scale)
    new_h = int(h * scale)
    resized_frame = cv2.resize(frame, (new_w, new_h))

    # 创建目标大小的画布并居中放置
    result = np.zeros((target_height, target_width, 3), dtype=frame.dtype)
    y_offset = (target_height - new_h) // 2
    x_offset = (target_width - new_w) // 2
    result[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized_frame

    return result

def insert_materials(main_video_path, insert_list, output_path):
    logging.info("开始视频剪辑流程")

    temp_files = []

    # 获取主视频信息
    try:
        main_info = get_video_info(main_video_path)
        main_duration = main_info['duration']
        main_width = main_info['width']
        main_height = main_info['height']
        main_fps = main_info['fps']
        logging.info(f"主视频时长: {main_duration:.2f} 秒，分辨率: [{main_width}, {main_height}]，帧率: {main_fps}")
    except Exception as e:
        logging.error(f"主视频加载失败: {main_video_path}, 错误: {str(e)}")
        # 尝试修复主视频
        fixed_main_path = robust_convert_video(main_video_path)
        if not fixed_main_path:
            logging.error("无法修复主视频，终止处理")
            return False

        temp_files.append(fixed_main_path)
        try:
            main_info = get_video_info(fixed_main_path)
            main_duration = main_info['duration']
            main_width = main_info['width']
            main_height = main_info['height']
            main_fps = main_info['fps']
            logging.info(f"修复后主视频加载成功: {fixed_main_path}")
        except Exception as e2:
            logging.error(f"修复后主视频仍无法加载: {fixed_main_path}, 错误: {str(e2)}")
            return False

    # 按时间排序
    sorted_insert_list = sorted(insert_list, key=lambda x: x['time'])

    # 创建临时输出文件
    temp_dir = tempfile.gettempdir()
    temp_output = os.path.join(temp_dir, f"temp_{os.path.basename(output_path)}")

    try:
        # 使用OpenCV逐帧处理视频
        logging.info("开始逐帧处理视频...")

        # 打开主视频
        main_cap = cv2.VideoCapture(main_video_path)
        if not main_cap.isOpened():
            logging.error(f"无法打开主视频: {main_video_path}")
            return False

        # 准备素材视频捕获器列表
        material_caps = []
        material_info_list = []
        material_playback_state = []  # 用于跟踪素材播放状态

        for item in sorted_insert_list:
            material_path = item['material_path']
            insert_time = item['time']

            if not os.path.exists(material_path):
                logging.error(f"素材文件不存在: {material_path}")
                material_caps.append(None)
                material_info_list.append(None)
                material_playback_state.append(None)
                continue

            try:
                # 获取素材信息
                mat_info, fixed_path = safe_load_clip(material_path)

                if fixed_path:
                    temp_files.append(fixed_path)
                    material_path = fixed_path

                # 打开素材视频
                cap = cv2.VideoCapture(material_path)
                if not cap.isOpened():
                    logging.error(f"无法打开素材视频: {material_path}")
                    material_caps.append(None)
                    material_info_list.append(None)
                    material_playback_state.append(None)
                    continue

                # 使用素材的完整时长，但不超过主视频剩余时间
                play_duration = min(mat_info['duration'], main_duration - insert_time)

                material_caps.append(cap)
                material_info_list.append({
                    'info': mat_info,
                    'start_time': insert_time,
                    'end_time': insert_time + play_duration,  # 素材结束时间
                    'duration': play_duration
                })
                # 初始化播放状态
                material_playback_state.append({
                    'is_playing': False,
                    'played_frames': 0
                })

                logging.info(f"准备插入素材: {material_path}，开始时间: {insert_time:.2f}s，结束时间: {insert_time + play_duration:.2f}s，时长: {play_duration:.2f}s")

            except Exception as e:
                logging.error(f"素材处理失败: {material_path}，错误: {str(e)}")
                material_caps.append(None)
                material_info_list.append(None)
                material_playback_state.append(None)
                continue

        # 创建输出视频写入器
        fourcc = cv2.VideoWriter_fourcc(*'mp4v')
        out = cv2.VideoWriter(temp_output, fourcc, main_fps, (main_width, main_height))

        if not out.isOpened():
            logging.error("无法创建输出视频文件")
            return False

        # 逐帧处理
        frame_count = 0
        current_time = 0
        frame_duration = 1.0 / main_fps

        # 进度条
        total_frames = int(main_duration * main_fps)
        pbar = tqdm(total=total_frames, desc="处理视频帧")

        while True:
            ret, frame = main_cap.read()
            if not ret:
                break

            # 查找当前时间应该播放的素材（优先选择时间靠后的素材）
            current_material_index = -1
            for i, (mat_info, playback_state) in enumerate(zip(material_info_list, material_playback_state)):
                if mat_info is None or playback_state is None:
                    continue

                # 检查当前时间是否在素材播放范围内
                if mat_info['start_time'] <= current_time <= mat_info['end_time']:
                    current_material_index = i

            # 如果找到了应该播放的素材
            if current_material_index >= 0:
                cap = material_caps[current_material_index]
                mat_info = material_info_list[current_material_index]
                playback_state = material_playback_state[current_material_index]

                if cap is not None:
                    # 如果是刚开始播放，重置素材视频到第一帧
                    if not playback_state['is_playing']:
                        playback_state['is_playing'] = True
                        playback_state['played_frames'] = 0
                        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
                        logging.debug(f"开始播放素材 {current_material_index} 于时间 {current_time:.2f}s")

                    # 连续读取素材帧
                    mat_ret, mat_frame = cap.read()

                    if mat_ret:
                        # 更新已播放帧数
                        playback_state['played_frames'] += 1
                        # 调整素材帧大小并居中
                        mat_frame = resize_and_center_frame(mat_frame, main_width, main_height)
                        # 叠加素材帧到主帧上
                        frame = mat_frame
                    else:
                        # 如果素材读取失败，尝试重置到第一帧再试一次
                        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
                        mat_ret, mat_frame = cap.read()
                        if mat_ret:
                            playback_state['played_frames'] += 1
                            mat_frame = resize_and_center_frame(mat_frame, main_width, main_height)
                            frame = mat_frame

            # 重置其他素材的播放状态
            for i, (mat_info, playback_state) in enumerate(zip(material_info_list, material_playback_state)):
                if mat_info is None or playback_state is None:
                    continue

                # 如果当前素材不在播放时间范围内，重置播放状态
                if not (mat_info['start_time'] <= current_time <= mat_info['end_time']):
                    if playback_state['is_playing']:
                        playback_state['is_playing'] = False
                        logging.debug(f"停止播放素材 {i} 于时间 {current_time:.2f}s，共播放 {playback_state['played_frames']} 帧")

            # 写入帧到输出视频
            out.write(frame)

            # 更新进度
            frame_count += 1
            current_time += frame_duration
            pbar.update(1)

        # 释放资源
        pbar.close()
        main_cap.release()
        out.release()

        # 关闭所有素材视频捕获器
        for cap in material_caps:
            if cap is not None:
                cap.release()

        # 复制音频轨道
        logging.info("正在处理音频...")
        audio_cmd = [
            'ffmpeg', '-y', '-i', temp_output, '-i', main_video_path,
            '-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', '-shortest',
            f"{temp_output}_with_audio.mp4"
        ]

        try:
            subprocess.run(audio_cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            shutil.move(f"{temp_output}_with_audio.mp4", temp_output)
        except Exception as e:
            logging.warning(f"音频处理失败，输出视频将没有音频: {str(e)}")

        # 移动临时文件到最终位置
        shutil.move(temp_output, output_path)
        logging.info(f"视频输出完成: {output_path}")
        logging.info("视频剪辑完成")
        return True

    except Exception as e:
        logging.error(f"视频写入失败: {str(e)}")
        return False
    finally:
        # 清理临时文件
        for temp_file in temp_files:
            if os.path.exists(temp_file):
                try:
                    os.remove(temp_file)
                    logging.info(f"清理临时文件: {temp_file}")
                except Exception as e:
                    logging.warning(f"无法删除临时文件: {temp_file}，错误: {str(e)}")
